package project;

import java.util.Date;
import java.util.List;

public class Minimax {

	protected static final int INFINITY = 1000000;

	private static int maxDepth;
	private static int timeout;
	private static int depth;
	private static int player;
	private static long startTime;
	private static Move bestSoFar;
	private static int[] wallval = { 0, 2, 4, 6, 8, 10, 12, 13, 14, 15, 16 };

	private static long getTime() {
		return new Date().getTime();
	}

	private static int heuristic(GameState game) {

		int myDist = Astar.findDist(player, game);
		int opDist = Astar.findDist(1 - player, game);

		if (opDist <= 1)
			return -INFINITY;

		if (myDist == 0)
			return INFINITY;

		return opDist - wallval[game.players[1 - player].getWallCount()]
				- myDist + wallval[game.players[player].getWallCount()];
	}

	private static int maximax(GameState game, int depth, int alpha, int beta) {

		if (getTime() - startTime - timeout >= 0)
			return depth % 2 == 0 ? -INFINITY : INFINITY;

		if (depth == Minimax.depth) {
			return depth % 2 == 0 ? heuristic(game) : -heuristic(game);
		}

		if (game.gameOver() != GameState.GAMENOTOVER) {
			if (game.gameOver() == player)
				return depth % 2 == 0 ? INFINITY : -INFINITY;
			return depth % 2 == 0 ? -INFINITY : INFINITY;
		}

		List<Move> movesList = game.validMoves();
		GameState next;
		int temp;

		for (Move move : movesList) {
			next = game.clone();
			next.applyMove(move);

			temp = -maximax(next, depth + 1, -beta, -alpha);

			if (temp > alpha) {
				alpha = temp;
				if (depth == 0) {
					bestSoFar = move;
				}
			}
			if (beta <= alpha)
				return alpha;
		}

		return alpha;
	}

	/**
	 * Calls minimax on a particular GameState to find the best move for a
	 * specific player
	 * 
	 * @param game
	 *            The game in which you want to perform minimax on
	 * @param player
	 *            The player who will be the "max" player
	 * @return The move that the "max" player should take
	 */
	public static Move bestMove(GameState game, int player) {

		int i;
		Minimax.player = player;
		int lastResult = -INFINITY;
		int currResult = -INFINITY;
		Move lastMove = null;
		startTime = getTime();

		for (i = 1; i <= maxDepth && getTime() - startTime - timeout < 0; i++) {
			Minimax.depth = i;
			lastMove = bestSoFar;
			lastResult = currResult;
			currResult = maximax(game, 0, -INFINITY - 1, INFINITY + 1);
		}

		return currResult >= lastResult ? bestSoFar : lastMove;
	}

	/**
	 * Changes the depth setting for Minimax. This will determine how many moves
	 * minimax will look ahead when attempting to calculate the best move. More
	 * depth indicates a harder difficulty
	 * 
	 * @param depth
	 *            The maximal depth of the search tree
	 */
	public static void setDepth(int depth) {
		Minimax.maxDepth = depth;
	}

	/**
	 * Changes the timeout setting for Minimax. This determines how long minimax
	 * will run for before exiting. A longer timeout gives a harder difficulty.
	 * 
	 * @param timeout
	 *            The maximum amount of time, in milliseconds, that the
	 *            algorithm should run for
	 */
	public static void setTimeout(int timeout) {
		Minimax.timeout = timeout;
	}

	public static int getMaxDepth() {
		return maxDepth;
	}

	public static int getTimeout() {
		return timeout;
	}
}
